﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using itau.im.entidade;
using itau.im.negocio;
using EnterpriseLibraryWrapper;

namespace itau.im.gerador
{
    public class GeradorDeCurvaPMTCDI : IGeradorDeCurva
    {

        #region Constantes

        private const int SITUACAO_LIBERADA = 40;
        private const int SITUACAO_EM_EXECUCAO = 500;

        private const int TIPOPRAZO_OVER = 4;
        private const int TIPOPRAZO_30 = 3;
        private const int TIPOPRAZO_360 = 2;
        private const int TIPOPRAZO_252 = 1;

        private const int TIPO_TAXA_EXPONENCIAL = 1;
        private const int TIPO_TAXA_LINEAR = 2;
        private const int TIPO_TAXA_FATOR_DIARIO = 3;
        private const int TIPO_TAXA_PU = 4;
        private const int TIPO_TAXA_CONSTANTE = 5;
        private const int TIPO_TAXA_CUBIC_SPLINE = 6;
        private const int TIPO_TAXA_TAXA = 7;

        private const int TIPO_PONTO_ANTEPOLACAO = 1;
        private const int TIPO_PONTO_INTERPOLACAO = 2;
        private const int TIPO_PONTO_EXTRAPOLACAO = 3;
        private const int TIPO_PONTO_VERTICE = 4;

        private const int INDEXADOR_CDI = 4;
        private const int INDEXADOR_CDIPLUS = 5;

        #endregion

        #region Atributos

        ExecucaoCurvaBS _execucaoCurvaBS;
        CurvaBS _curvaBS;
        ConversorTaxas _conversor;
        CalculosPMT _calculos;

        IList<CurvaExecucaoPonto> _pontosBaseBullet;
        IList<CurvaExecucaoPonto> _pontosPMTCDI;
        IList<CurvaExecucaoPonto> _pontosBase;
        IList<PontoCalculoPMTCDI> pontos;
        IList<TipoPrazo> tiposPrazo;
        Curva _curvaBase;
        RegistroExecucaoCurva _registroExecucaoCurvaBase;
        Curva _curvaBaseBullet;
        RegistroExecucaoCurva _registroExecucaoCurvaBaseBullet;

        #endregion

        #region Propriedades

        public List<Item> MapaCofiguracoesGerador
        {
            get;
            set;
        }

        public RegistroExecucaoCurva RegistroExecucaoCurva
        {
            get;
            set;
        }

        public Curva Curva
        {
            get;
            set;
        }

        public int SituacaoAtual
        {
            get;
            set;
        }

        #endregion

        #region Construtor

        public GeradorDeCurvaPMTCDI()
        {
            _execucaoCurvaBS = new ExecucaoCurvaBS();
            _curvaBS = new CurvaBS();
            _conversor = new ConversorTaxas();
            _calculos = new CalculosPMT();
            tiposPrazo = _curvaBS.ObterTiposPrazo();
        }

        #endregion

        #region Destrutor

        ~GeradorDeCurvaPMTCDI()
        {
            _execucaoCurvaBS = null;
            _curvaBS = null;
            _conversor = null;
            _calculos = null;
            tiposPrazo = null;
        }

        #endregion

        #region Método Público

        public void Gerar()
        {
            try
            {
                GerarCurvaPMTCDI();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        #endregion

        #region Métodos Privados

        private void GerarCurvaPMTCDI()
        {
            try
            {
                AtualizarSituacaoExecucaoCurvaParaEmExecucao();

                ObterDadosCurvaBullet();

                ConverterCurvaBullet();

                ObterDadosCurvaBase();

                ConverterCurvaBase();

                MontarFluxo();

                CalcularTIR();

                GravarPontos();
            }
            catch (Exception ex)
            {
                LimparSujeiraExecucao();
                Exception exCurva = new Exception(string.Format("Não foi possível gerar a curva PMT CDI de código: {0} ({1}) e nome: {2}.", RegistroExecucaoCurva.CodigoCurva, RegistroExecucaoCurva.DataHoraExecucaoCurva, Curva.NomeCurva), ex);
                EnterpriseLibraryFactory.GetExceptionHandler().HandleException(exCurva, "ErrosGerador");
            }
        }

        private void AtualizarSituacaoExecucaoCurvaParaEmExecucao()
        {
            SituacaoAtual = RegistroExecucaoCurva.CodigoSituacao;
            AlterarSituacaoDaExecucao(SITUACAO_EM_EXECUCAO + SituacaoAtual);
            RegistroExecucaoCurva.CodigoSituacao = SituacaoAtual;
            Logger.GravarLog(string.Format("Processando a curva de código: {0} e nome: {1}", RegistroExecucaoCurva.CodigoCurva, Curva.NomeCurva));
        }

        private void ObterDadosCurvaBullet()
        {
            if (Curva.CurvaVigente.CodigoCurvaBasePMT == 0)
            {
                throw new Exception(string.Format("Não foi possível encontrar a curva Bullet para esta PMT. Curva PMT de código: {0} e nome: {1}", RegistroExecucaoCurva.CodigoCurva, Curva.NomeCurva));
            }

            _curvaBaseBullet = _curvaBS.ObterCurva(Curva.CurvaVigente.CodigoCurvaBasePMT);

            _registroExecucaoCurvaBaseBullet = _execucaoCurvaBS.ObterRegistroExecucaoCurva(_curvaBaseBullet.CurvaVigente.CodigoCurvaVigente,
                _curvaBaseBullet.CurvaVigente.DataInclusao, RegistroExecucaoCurva.DataHoraExecucaoCurva);

            if (_registroExecucaoCurvaBaseBullet.CodigoCurva == 0)
            {
                _registroExecucaoCurvaBaseBullet = _execucaoCurvaBS.ObterUltimaExecucaoCurva(_curvaBaseBullet.CurvaVigente.CodigoCurvaVigente,
                    _curvaBaseBullet.CurvaVigente.DataInclusao, RegistroExecucaoCurva.DataHoraExecucaoCurva.Date.AddHours(23).AddMinutes(59).AddSeconds(59), SITUACAO_LIBERADA);
            }

            if (_registroExecucaoCurvaBaseBullet.CodigoSituacao == SITUACAO_LIBERADA
                && RegistroExecucaoCurva.DataHoraExecucaoCurva.Date == _registroExecucaoCurvaBaseBullet.DataHoraExecucaoCurva.Date)
            {
                _pontosBaseBullet = _execucaoCurvaBS.ObterVerticesCurvaVigente(_curvaBaseBullet.CurvaVigente.CodigoCurvaVigente,
                    _registroExecucaoCurvaBaseBullet.DataInclusaoVigencia, _registroExecucaoCurvaBaseBullet.DataHoraExecucaoCurva);
            }
            else
            {
                throw new Exception(string.Format("Não foi possível obter a execução da curva Bullet. Curva PMT de código: {0} e nome: {1}", RegistroExecucaoCurva.CodigoCurva, Curva.NomeCurva));
            }
        }

        private void ConverterCurvaBullet()
        {
            TaxaPontoCurvaVigente tipoVertice = _curvaBaseBullet.CurvaVigente.lstTaxaPontoCurvaVigente.FirstOrDefault(t => t.CodigoTipoPontoCurva.Equals(TIPO_PONTO_VERTICE));

            if (tipoVertice != null && tipoVertice.CodigoCurva > 0)
            {
                if (tipoVertice.CodigoTipoTaxa.Equals(TIPO_TAXA_TAXA))
                {
                    if (_curvaBaseBullet.CurvaVigente.CodigoTipoPrazo.Equals(TIPOPRAZO_OVER))
                    {
                        _pontosBaseBullet = _conversor.ConverterTaxasOverParaFatorDiario(_pontosBaseBullet);
                    }
                    else
                    {
                        if (_curvaBaseBullet.CurvaVigente.CodigoTipoTaxa == TIPO_TAXA_EXPONENCIAL)
                        {
                            _pontosBaseBullet = _conversor.ConverterTaxasExponenciaisParaFatorDiario(_pontosBaseBullet, tiposPrazo.Where(c => c.Codigo == _curvaBaseBullet.CurvaVigente.CodigoTipoPrazo).ToList()[0].QuantidadeDiasAno);
                        }
                        else if (_curvaBaseBullet.CurvaVigente.CodigoTipoTaxa == TIPO_TAXA_LINEAR)
                        {
                            _pontosBaseBullet = _conversor.ConverterTaxasLinearesParaFatorDiario(_pontosBaseBullet, tiposPrazo.Where(c => c.Codigo == _curvaBaseBullet.CurvaVigente.CodigoTipoPrazo).ToList()[0].QuantidadeDiasAno);
                        }
                    }
                }
                else if (tipoVertice.CodigoTipoTaxa.Equals(TIPO_TAXA_PU))
                {
                    _pontosBaseBullet = _conversor.ConverterPUsParaFatorDiario(_pontosBaseBullet);
                }
            }
        }

        private void ObterDadosCurvaBase()
        {
            _curvaBase = _curvaBS.ObterCurvaBase(Curva.CurvaVigente.CodigoCurvaBasePMT);

            if (_curvaBase.CodigoCurva == 0)
            {
                throw new Exception(string.Format("Não foi possível obter a curva base da PMT. Curva PMT de código: {0} e nome: {1}", RegistroExecucaoCurva.CodigoCurva, Curva.NomeCurva));
            }

            _registroExecucaoCurvaBase = _execucaoCurvaBS.ObterRegistroExecucaoCurva(_curvaBase.CurvaVigente.CodigoCurvaVigente,
                _curvaBase.CurvaVigente.DataInclusao, RegistroExecucaoCurva.DataHoraExecucaoCurva);

            if (_registroExecucaoCurvaBase.CodigoCurva == 0)
            {
                _registroExecucaoCurvaBase = _execucaoCurvaBS.ObterUltimaExecucaoCurva(_curvaBase.CurvaVigente.CodigoCurvaVigente,
                    _curvaBase.CurvaVigente.DataInclusao, RegistroExecucaoCurva.DataHoraExecucaoCurva.Date.AddHours(23).AddMinutes(59).AddSeconds(59), SITUACAO_LIBERADA);
            }

            if (_registroExecucaoCurvaBase.CodigoSituacao == SITUACAO_LIBERADA
                && RegistroExecucaoCurva.DataHoraExecucaoCurva.Date == _registroExecucaoCurvaBase.DataHoraExecucaoCurva.Date)
            {
                _pontosBase = _execucaoCurvaBS.ObterVerticesCurvaVigente(_curvaBase.CurvaVigente.CodigoCurvaVigente,
                    _registroExecucaoCurvaBase.DataInclusaoVigencia, _registroExecucaoCurvaBase.DataHoraExecucaoCurva);
            }
            else
            {
                throw new Exception(string.Format("Não foi possível obter a execução da curva Base. Curva PMT de código: {0} e nome: {1}", RegistroExecucaoCurva.CodigoCurva, Curva.NomeCurva));
            }
        }

        private void ConverterCurvaBase()
        {
            TaxaPontoCurvaVigente tipoVertice = _curvaBase.CurvaVigente.lstTaxaPontoCurvaVigente.FirstOrDefault(t => t.CodigoTipoPontoCurva.Equals(TIPO_PONTO_VERTICE));

            if (tipoVertice != null && tipoVertice.CodigoCurva > 0)
            {
                if (tipoVertice.CodigoTipoTaxa.Equals(TIPO_TAXA_TAXA))
                {
                    if (_curvaBaseBullet.CurvaVigente.CodigoTipoPrazo.Equals(TIPOPRAZO_OVER))
                    {
                        _pontosBase = _conversor.ConverterTaxasOverParaFatorDiario(_pontosBase);
                    }
                    else
                    {
                        if (_curvaBase.CurvaVigente.CodigoTipoTaxa == TIPO_TAXA_EXPONENCIAL)
                        {
                            _pontosBase = _conversor.ConverterTaxasExponenciaisParaFatorDiario(_pontosBase, tiposPrazo.Where(c => c.Codigo == _curvaBase.CurvaVigente.CodigoTipoPrazo).ToList()[0].QuantidadeDiasAno);
                        }
                        else if (_curvaBaseBullet.CurvaVigente.CodigoTipoTaxa == TIPO_TAXA_LINEAR)
                        {
                            _pontosBase = _conversor.ConverterTaxasLinearesParaFatorDiario(_pontosBase, tiposPrazo.Where(c => c.Codigo == _curvaBase.CurvaVigente.CodigoTipoPrazo).ToList()[0].QuantidadeDiasAno);
                        }
                    }
                }
                else if (tipoVertice.CodigoTipoTaxa.Equals(TIPO_TAXA_PU))
                {
                    _pontosBase = _conversor.ConverterPUsParaFatorDiario(_pontosBase);
                }
            }
        }

        private double ConverterValorTIR(double valorTIR, int dias, int baseDias, TaxaPontoCurvaVigente tipoVertice)
        {
            double resultado = 0;

            if (tipoVertice != null && tipoVertice.CodigoCurva > 0)
            {
                if (tipoVertice.CodigoTipoTaxa.Equals(TIPO_TAXA_TAXA)
                    || tipoVertice.CodigoTipoTaxa.Equals(TIPO_TAXA_FATOR_DIARIO))
                {
                    if (_curvaBaseBullet.CurvaVigente.CodigoTipoPrazo.Equals(TIPOPRAZO_OVER))
                    {
                        resultado = _conversor.ConverterTaxaOverParaFatorDiario(valorTIR, dias);
                    }
                    else
                    {
                        if (_curvaBaseBullet.CurvaVigente.CodigoTipoTaxa == TIPO_TAXA_EXPONENCIAL)
                        {
                            resultado = _conversor.ConverterTaxaExponencialParaFatorDiario(valorTIR, baseDias, dias);
                        }
                        else if (_curvaBaseBullet.CurvaVigente.CodigoTipoTaxa == TIPO_TAXA_LINEAR)
                        {
                            resultado = _conversor.ConverterTaxaLinearParaFatorDiario(valorTIR, baseDias, dias);
                        }
                    }
                }
                else if (tipoVertice.CodigoTipoTaxa.Equals(TIPO_TAXA_PU))
                {
                    resultado = _conversor.ConverterPUParaFatorDiario(valorTIR);
                }
            }

            return resultado;
        }

        private void MontarFluxo()
        {
            DateTime dataVencimento = RegistroExecucaoCurva.DataHoraExecucaoCurva;
            int anosExtensao = 0;

            if (_curvaBaseBullet.QuantidadeAnoExtensao != 0
               && _curvaBase.QuantidadeAnoExtensao != 0)
            {
                anosExtensao = _curvaBaseBullet.QuantidadeAnoExtensao > _curvaBase.QuantidadeAnoExtensao ? _curvaBase.QuantidadeAnoExtensao : _curvaBaseBullet.QuantidadeAnoExtensao;
            }
            else if (_curvaBaseBullet.QuantidadeAnoExtensao > 0)
            {
                anosExtensao = _curvaBaseBullet.QuantidadeAnoExtensao;
            }
            else if (_curvaBase.QuantidadeAnoExtensao > 0)
            {
                anosExtensao = _curvaBase.QuantidadeAnoExtensao;
            }
            else if (_pontosBase != null && _pontosBaseBullet != null)
            {
                int diasCorridos = 0;

                if (_pontosBase.Count == _pontosBaseBullet.Count)
                {
                    diasCorridos = _pontosBase.Count;
                }
                else if (_pontosBase.Count > _pontosBaseBullet.Count && _pontosBaseBullet.Count != 0)
                {
                    diasCorridos = _pontosBaseBullet[_pontosBaseBullet.Count - 1].QuantidadeDiasCorridos;
                }
                else if (_pontosBaseBullet.Count > _pontosBase.Count && _pontosBase.Count != 0)
                {
                    diasCorridos = _pontosBase[_pontosBase.Count - 1].QuantidadeDiasCorridos;
                }

                int resto = 0;
                anosExtensao = Math.DivRem(diasCorridos, 365, out resto);
            }

            CalendarioReguaPMTBS _reguaCurva = new CalendarioReguaPMTBS(dataVencimento, anosExtensao);
            ConversorReguaParaPontoCalculoPMTCDI conversorPontos = new ConversorReguaParaPontoCalculoPMTCDI();
            pontos = conversorPontos.Converter(_reguaCurva);
        }

        private void CalcularTIR()
        {
            int contadorParcelas = 1;

            foreach (PontoCalculoPMTCDI ponto in pontos)
            {
                CurvaExecucaoPonto pontoBullet = _pontosBaseBullet.FirstOrDefault(p => p.QuantidadeDiasCorridos == ponto.DiasCorridos);

                TaxaPontoCurvaVigente tipoVerticeBullet = _curvaBaseBullet.CurvaVigente.lstTaxaPontoCurvaVigente.FirstOrDefault(t => t.CodigoTipoPontoCurva.Equals(TIPO_PONTO_VERTICE));

                if (pontoBullet != null
                    && pontoBullet.CodigoCurva > 0)
                {
                    double estimativaTIRBullet = CalcularFluxos(contadorParcelas, tipoVerticeBullet, _pontosBaseBullet, _curvaBaseBullet);

                    CurvaExecucaoPonto pontoBase = _pontosBase.FirstOrDefault(p => p.QuantidadeDiasCorridos == ponto.DiasCorridos);

                    if (pontoBase != null
                    && pontoBase.CodigoCurva > 0)
                    {
                        TaxaPontoCurvaVigente tipoVerticeBase = _curvaBase.CurvaVigente.lstTaxaPontoCurvaVigente.FirstOrDefault(t => t.CodigoTipoPontoCurva.Equals(TIPO_PONTO_VERTICE));

                        double estimativaTIRBase = CalcularFluxos(contadorParcelas, tipoVerticeBase, _pontosBase, _curvaBase);

                        ponto.ValorVertice = RetornarValorCDI(Curva, estimativaTIRBase, estimativaTIRBullet);
                    }

                    contadorParcelas++;
                }
            }

            ConversorPontoCalculoPMTCDIParaCurvaExecucaoPonto conversor = new ConversorPontoCalculoPMTCDIParaCurvaExecucaoPonto();
            _pontosPMTCDI = conversor.Converter(pontos, this.RegistroExecucaoCurva);
        }

        private double CalcularFluxos(int contadorParcelas, TaxaPontoCurvaVigente tipoVerticeBase, IList<CurvaExecucaoPonto> pontosBaseBullet, Curva curvaBaseBullet)
        {
            int somaDiasBase = 0;
            double somaValorFuturoBase = 0;
            double TIRInicialBase = 0;

            double estimativaTIRBase = 0;

            double somaVLPBase = 0;
            double diferencaSomaSaldoDevedorEstimativaTIRBase = 0;
            double diferencaSomaSaldoDevedorEstimativaTIRAnteriorBase = 0;
            int contadorEstimativasBase = 1;

            for (int i = 0; i < contadorParcelas; i++)
            {
                CurvaExecucaoPonto pontoBaseAtual = pontosBaseBullet.FirstOrDefault(p => p.QuantidadeDiasCorridos == pontos[i].DiasCorridos);
                CurvaExecucaoPonto pontoBaseAnterior = null;

                if (i != 0)
                {
                    pontoBaseAnterior = pontosBaseBullet.FirstOrDefault(p => p.QuantidadeDiasCorridos == pontos[i - 1].DiasCorridos);
                }
                else
                {
                    pontoBaseAnterior = pontosBaseBullet[0];
                }

                pontos[i].ValorFuturo = _calculos.CalcularValorFuturo(pontoBaseAtual.ValorVertice, pontoBaseAnterior.ValorVertice, contadorParcelas - i);
                somaValorFuturoBase += pontos[i].ValorFuturo;
                somaDiasBase += RetornarDias(_curvaBase.CurvaVigente, pontos[i]);
            }

            TIRInicialBase = _calculos.CalcularTIR(somaDiasBase, contadorParcelas, somaValorFuturoBase, contadorParcelas, RetornarBaseDias(curvaBaseBullet.CurvaVigente));

            for (int j = 0; j < contadorParcelas; j++)
            {
                somaVLPBase += _calculos.CalcularValorPresente(pontos[j].ValorFuturo, ConverterValorTIR(TIRInicialBase * 100, RetornarDias(curvaBaseBullet.CurvaVigente, pontos[j]), RetornarBaseDias(curvaBaseBullet.CurvaVigente), tipoVerticeBase));
            }

            diferencaSomaSaldoDevedorEstimativaTIRBase = contadorParcelas - somaVLPBase;

            if (Math.Round(diferencaSomaSaldoDevedorEstimativaTIRBase, 10) == 0.0)
            {
                estimativaTIRBase = TIRInicialBase;
            }

            while ((Math.Round(diferencaSomaSaldoDevedorEstimativaTIRBase, 10) > 0.0 || Math.Round(diferencaSomaSaldoDevedorEstimativaTIRBase, 10) < 0.0)
            && contadorEstimativasBase <= 15
            && diferencaSomaSaldoDevedorEstimativaTIRBase != diferencaSomaSaldoDevedorEstimativaTIRAnteriorBase)
            {
                somaVLPBase = 0;

                if (contadorEstimativasBase == 1)
                {
                    if (diferencaSomaSaldoDevedorEstimativaTIRBase < 0)
                    {
                        estimativaTIRBase = TIRInicialBase * 2;
                    }
                    else
                    {
                        estimativaTIRBase = (double)TIRInicialBase / 2;
                    }
                }
                else
                {
                    double reserva = estimativaTIRBase;
                    estimativaTIRBase = _calculos.AproximarValorTIR(TIRInicialBase, estimativaTIRBase, diferencaSomaSaldoDevedorEstimativaTIRBase, diferencaSomaSaldoDevedorEstimativaTIRAnteriorBase);
                    TIRInicialBase = reserva;
                }

                // Iteração do Fluxo tendo com o resultado um valor VP mais próximo
                for (int j = 0; j < contadorParcelas; j++)
                {
                    somaVLPBase += _calculos.CalcularValorPresente(pontos[j].ValorFuturo, ConverterValorTIR(estimativaTIRBase * 100, RetornarDias(curvaBaseBullet.CurvaVigente, pontos[j]), RetornarBaseDias(curvaBaseBullet.CurvaVigente), tipoVerticeBase));
                }

                diferencaSomaSaldoDevedorEstimativaTIRAnteriorBase = diferencaSomaSaldoDevedorEstimativaTIRBase;
                diferencaSomaSaldoDevedorEstimativaTIRBase = contadorParcelas - somaVLPBase;
                contadorEstimativasBase++;
            }
            return estimativaTIRBase;
        }

        private double RetornarValorCDI(Curva curva, double TIRBase, double TIRBullet)
        {
            if (curva.CodigoCategoriaMercado.Equals(INDEXADOR_CDI))
            {
                return _calculos.CalcularPercentualCDI(TIRBullet, TIRBase);
            }
            else if (curva.CodigoCategoriaMercado.Equals(INDEXADOR_CDIPLUS))
            {
                return _calculos.CalcularCDIMais(TIRBullet, TIRBase);
            }

            return 0;
        }

        private void LimparSujeiraExecucao()
        {
            RegistroExecucaoCurva.CodigoSituacao = SituacaoAtual;
            FilaExcecao.Adicionar(RegistroExecucaoCurva);
        }

        private int RetornarBaseDias(CurvaVigente _curvaVigente)
        {
            if (_curvaVigente.CodigoTipoPrazo.Equals(TIPOPRAZO_30)
                || _curvaVigente.CodigoTipoPrazo.Equals(TIPOPRAZO_OVER))
            {
                return 30;
            }
            else if (_curvaVigente.CodigoTipoPrazo.Equals(TIPOPRAZO_360))
            {
                return 360;
            }
            else
            {
                return 252;
            }
        }

        private int RetornarDias(CurvaVigente _curvaVigente, PontoCalculoPMTCDI ponto)
        {
            if (_curvaVigente.CodigoTipoPrazo.Equals(TIPOPRAZO_252))
            {
                return ponto.DiasUteis;
            }
            else
            {
                return ponto.DiasCorridos;
            }
        }

        private void GravarPontos()
        {
            RegistroExecucaoCurva.CodigoSituacao = SITUACAO_LIBERADA;
            _execucaoCurvaBS.PersistirExecucao(_pontosPMTCDI, RegistroExecucaoCurva);
        }

        private void AlterarSituacaoDaExecucao(int codigoSituacao)
        {
            RegistroExecucaoCurva.CodigoSituacao = codigoSituacao;
            _execucaoCurvaBS.AtualizarRegistroExecucaoCurva(RegistroExecucaoCurva);
        }

        #endregion

    }
}
